local skynet = require "skynet"
local mysql = require "skynet.db.mysql"
local socketchannel = require "skynet.socketchannel"

local persistent_types = {
    save_user_data = "save_user_data",
}

local mode = ... 

if mode == "slave" then 
    -- slave 
    local master
    
    local db 
    local mysql_host = skynet.getenv("mysql_host") or "127.0.0.1"
    local mysql_port = tonumber(skynet.getenv("mysql_port") or 3306)
    local mysql_username = skynet.getenv("mysql_username") or "cauchy"
    local mysql_password = skynet.getenv("mysql_password") or "root"
    local mysql_database = skynet.getenv("mysql_database") or "chat"

    local function mysql_query(sql, multirows) 
        local res = db:query(sql)
        local result = false 
        
        if res and res['errcode'] then 
            error("mysql error : ", sql)
            return nil 
        end 

        if multirows then 
            -- 返回多行数据
            if #res >= 1 then 
                result = {} 
                for k, v in pairs(res) do 
                    result[#result + 1] = v 
                end 
            end 
        else 
            -- 返回一行数据
            if res and res[1] then 
                result = res[1]
            end 
        end 

        return result
    end 
    
    local CMD = {}
    local REQUEST_HANDLER = {}

    function REQUEST_HANDLER.save_user_data(task)
        local uid = task.uid 
        local user_info = task.data 

        local sql

        local exp = user_info.exp 
        local now = util.get_current_time()

        -- 
        local fmt = [===[
        update users set `exp` = %d, `updated` = '%s' where `uid` = '%d' limit 1
        ]===]
        
        sql = string.format(fmt, exp, now, uid)

        res = mysql_query(sql)

        return true 
    end 
    
    -- 做持久化任务
    --      任务失败：不会将master中task_queue的task删除，会在之后继续轮询
    function CMD.do_persistent(taskid, task) 
        local task_type
        if task then task_type = task.type end 
        if REQUEST_HANDLER[task_type] then 
            local f = REQUEST_HANDLER[task_type]
            local result = f(task) 
            if result then 
                skynet.send(master, "lua", "finish_task", taskid)
            end
        end 
    end 

    function CMD.load_user_data(uid) 
        local user_info = {}

        local fmt = [===[
        select * from chat.users where `uid` = '%d' limit 1
        ]===]

        local sql = string.format(fmt, uid)
        local row = mysql_query(sql)

        if row == false then 
            error("try to load non-exists user uid")
            return false 
        end 
        
        local uid = row['uid']
        local username = row['username']
        local password = row['password']
        local exp = row['exp']
        local updated = row['updated']

        user_info = {
            uid = uid, 
            username = username,
            password = password,
            exp = exp, 
            updated = updated,
        }

        return user_info
    end 

    function CMD.init(persistent_master)
        master = persistent_master
    end 

    skynet.start(function()
        skynet.dispatch("lua", function(_, _, cmd, ...)
            local f = assert(CMD[cmd])
            skynet.ret(skynet.pack(f(...)))
        end)

        -- 数据库连接成功后，执行一些初始化操作或设置特定的会话参数
        local function on_connected()

        end 

        local ok 
        ok, db = pcall(mysql.connect, {
            host = mysql_host, 
            port = mysql_port, 
            database = mysql_database,
            user = mysql_username, 
            password = mysql_password,
            max_packet_size = 1024 * 1024, 
            charset = "utf8",
            on_connect = on_connected
        })

        if not ok or not db then 
            error("db connect failed ~")
        end 

        -- keep db alive 
        skynet.fork(function()
            while true do 
                if db and db ~= socketchannel.error then 
                    db:ping()
                end 
            end 
            skynet.sleep(60 * 100) -- 60 s
        end)

    end)


else  -----------------------------------------------------------------------
    
    
    -- master 

    local slaves = {} 

    local task_queue = {} 
    --[[
        task: 
            {
                type,  -- 任务类型（persistent_types）
                id,    -- 唯一id
                uid,   -- 用户id
                data,  -- 用户数据
            }
    ]]
    local task_queue_uid2taskid = {} -- { uid : taskid }
    local task_queue_taskid2uid = {} -- { taskid : uid }
    
    local task_id = 0
    local balance = 1

    local function get_task_id()
        local id = task_id
        task_id = task_id + 1
        return id 
    end 

    -- 轮询获取从persistent服务
    local function get_persistent_slave()
        local slave = slaves[balance]
        balance = balance + 1
        if balance > #slaves then 
            balance = 1
        end 
        return slave 
    end 

    local function is_userdata_pending_persistent(uid)
        if task_queue_uid2taskid[uid] then 
            return task_queue_uid2taskid[uid]
        end 
        return false 
    end 

    -- 默认：do_persistent 持久化任务执行
    local function process_task_queue()
        for taskid, task in pairs(task_queue) do
            local slave = get_persistent_slave()
            -- print(slave, task.type, task.id, task.uid)
            skynet.send(slave, "lua", "do_persistent", taskid, task)
        end 
        skynet.timeout(100, process_task_queue)
    end 


    local CMD = {}

    -- call by libpersistent 
    function CMD.load_user_data(cmd, uid)
        local result
        local task_id = is_userdata_pending_persistent(uid) 
        if task_id then 
            local userdata = task_queue[task_id]
            result = userdata.data 
        else 
            local slave = get_persistent_slave()
            result = skynet.call(slave, "lua", cmd, uid)
        end 

        return result
    end 

    -- call by libpersistent
    -- 任务队列（task_queue）中：
    --      对于同一个用户，我只保存最后一次
    function CMD.save_user_data(cmd, uid, userinfo)
        local old_task_id = task_queue_uid2taskid[uid] 
        local new_task_id = get_task_id()

        if old_task_id then 
            task_queue_uid2taskid[uid] = nil 
            task_queue_taskid2uid[old_task_id] = nil 
            task_queue[old_task_id] = nil 
        end 

        local task = {
            id = new_task_id, 
            type = persistent_types.save_user_data, 
            uid = uid, 
            data = userinfo, 
        }

        task_queue[new_task_id] = task -- 封装task进task_queue
        task_queue_uid2taskid[uid] = new_task_id
        task_queue_taskid2uid[new_task_id] = uid 
    end 

    -- call by slave
    function CMD.finish_task(cmd, taskid)
        local task = task_queue[taskid]
        if task then 
            task_queue[taskid] = nil 
            local uid = task_queue_taskid2uid[taskid]
            if uid then 
                task_queue_taskid2uid[taskid] = nil 
                task_queue_uid2taskid[uid] = nil 
            end 
        end 
    end 

    skynet.start(function()
        local slave_size = skynet.getenv("persistent_slave_poolsize") or 8
        slave_size = tonumber(slave_size)

        for i = 1, slave_size do 
            local slave = skynet.newservice(SERVICE_NAME, "slave")
            skynet.call(slave, "lua", "init", skynet.self())
            slaves[#slaves + 1] = slave 
        end 
        
        skynet.dispatch("lua", function(session, address, cmd, ...)
            local f = assert(CMD[cmd])
            skynet.ret(skynet.pack(f(cmd, ...)))
        end)

        skynet.timeout(100, process_task_queue)
    end)
end 